// Copyright (c) .NET Foundation and contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using ILLink.Shared;
using ILLink.Shared.TrimAnalysis;
using Mono.Cecil;

using TypeName = System.Reflection.Metadata.TypeName;

namespace Mono.Linker
{
    internal sealed partial class TypeNameResolver
    {
        public bool TryResolveTypeName(
            string typeNameString,
            in DiagnosticContext diagnosticContext,
            [NotNullWhen(true)] out TypeReference? typeReference,
            [NotNullWhen(true)] out List<TypeResolutionRecord>? typeResolutionRecords,
            bool needsAssemblyName)
        {
            typeReference = null;
            typeResolutionRecords = null;
            if (string.IsNullOrEmpty(typeNameString))
                return false;

            if (!TypeName.TryParse(typeNameString, out TypeName? parsedTypeName, s_typeNameParseOptions))
                return false;

            if (needsAssemblyName && !IsFullyQualified(parsedTypeName))
            {
                diagnosticContext.AddDiagnostic(DiagnosticId.TypeNameIsNotAssemblyQualified, typeNameString);
                return false;
            }

            // If parsedTypeName doesn't have an assembly name in it but it does have a namespace,
            // search for the type in the calling object's assembly. If not found, look in the core
            // assembly.
            ICustomAttributeProvider? provider = diagnosticContext.Origin.Provider;
            AssemblyDefinition? referencingAssembly = provider switch
            {
                AssemblyDefinition asm => asm,
                TypeDefinition type => type.Module?.Assembly,
                IMemberDefinition member => member.DeclaringType.Module.Assembly,
                null => null,
                _ => throw new NotSupportedException()
            };

            if (referencingAssembly is null)
                return false;

            typeResolutionRecords = new List<TypeResolutionRecord>();
            typeReference = ResolveTypeName(referencingAssembly, parsedTypeName, typeResolutionRecords);
            return typeReference != null;

            static bool IsFullyQualified(TypeName typeName)
            {
                if (typeName.AssemblyName is null)
                    return false;

                if (typeName.IsArray || typeName.IsPointer || typeName.IsByRef)
                    return IsFullyQualified(typeName.GetElementType());

                if (typeName.IsConstructedGenericType)
                {
                    foreach (var typeArgument in typeName.GetGenericArguments())
                    {
                        if (!IsFullyQualified(typeArgument))
                            return false;
                    }
                }

                return true;
            }
        }
    }
}
